package com.gitee.wsl.mathematics.geometry.shape

import com.gitee.wsl.ext.math.Dimensions
import com.gitee.wsl.mathematics.function.Line.Companion.withTwoPoints
import kotlin.math.abs
import kotlin.math.sqrt


open class Line(startPoint: Point = Point(),
                endPoint: Point = Point()): Dimension(startPoint,endPoint) {

    constructor(startX: Float, startY: Float, stopX: Float, stopY: Float): this(
        Point(startX,startY),
        Point(stopX,stopY)
    )

    open fun findNearlyPoint(touchX: Float, touchY: Float): Point? {
        listOf(startPoint,endPoint).forEach {
            if(Dimensions.isNearlyPoint(touchX, touchY, it, MIN_LENGTH))
                return it
        }
        return null
    }

    open fun isNearlyPoint(touchX: Float, touchY: Float): Boolean {
        return calcPointToLineDistance(touchX, touchY)< MIN_LENGTH
    }

    fun percentLine(percent: Float): Line {
        return Line(startPoint,percent(percent))
    }

    /**
     * 计算 p 到点 ab线的 最短距离
     * 将三个点 组成一个三角形，计算出 三条边长(弧线) 用海伦公式可以算出三角形面积，
     *  C点到边c的距离=S*2/c
     */
    fun calcPointToLineDistance(touchX: Float, touchY: Float): Float {
        val p = Point(touchX,touchY)

        // 1、 计算出 三条边长 ab ap bp
        // 1、 计算出 三条边长 ab ap bp
        val ab = startPoint.distance(endPoint)
        val ap = startPoint.distance(p)
        val bp = endPoint.distance(p)

        var useSideLength = false

        // 判断是否取边长
        if (isObtuseTriangle(ab, ap, bp) && (ab < ap || ab < bp)) {
            useSideLength = true
        }

        var b=endPoint

        var s: Float = getMinDistance(startPoint, b, p, useSideLength)

        while (s.isNaN()) {
            b = (startPoint to b).center

            // p点 不变，a点不变，b点 变为  ab的中点
            s = getMinDistance(startPoint, b, p, useSideLength)
        }

        return s
    }

    /**
     * @param a             航线点a
     * @param b             航线点b
     * @param p             船位p
     */
    private fun getMinDistance(a: Point, b: Point, p: Point, useSideLength: Boolean): Float {
        // 1、 计算出 三条边长 ab ap bp
        val ab = a.distance(b)
        val ap = a.distance( p)
        val bp = b.distance( p)

        val S = (ab + ap + bp) / 2

        // 2、 求面积
        val area = sqrt(S * (S - ab) * (S - ap) * (S - bp)) //求面积

        // 如果点P对应的边长 无限趋紧于0 ，则取 长度ap bp中最小的
        if (ab == 0.0f) {
            return if (ap.compareTo(bp) > 0) bp else ap
        }


        // 如果不是 一个三角形，返回 NaN
        if (area.isNaN()) {
            return area
        }

        // 如果是钝角三角形,且 最长边 不是 点 P 对应的边 ，则取 与船的两条边长中的最短一条
        return if (isObtuseTriangle(ab, ap, bp) && (ab < ap || ab < bp) && useSideLength) {
            bp.coerceAtMost(ap)
        } else // 3、p点到边p的距离=S*2/p
            area * 2 / ab
    }


    /**
     * 判断这三条线组成的 三角形 是不是 钝角三角形
     */
    private fun isObtuseTriangle(a: Float, b: Float, c: Float): Boolean {
        // 如果一个三角形的最长边平方>其他两边的平方和，这个三角形是钝角三角形
        val doubles = floatArrayOf(a, b, c)
        doubles.sort()
        return doubles[2] * doubles[2] > doubles[1] * doubles[1] + doubles[0] * doubles[0]
    }

    /**
     * 判断是否平行
     */
    fun isParallel(anyLine: Line):Boolean{
        return (endPoint.y - startPoint.y) * (anyLine.endPoint.x - anyLine.startPoint.x) == (anyLine.endPoint.y - anyLine.startPoint.y)  * (endPoint.x - startPoint.x)
    }

    /**
     * 计算交点
     */
    fun getCrossPoint(anyLine: Line): Point {
        val point = Point()
        /**  前面直角的方法省略了 主要是判断斜线的交点 */
        val k1 = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x) //得到线段1的 斜率K的值
        val b1 = startPoint.y - startPoint.x * k1 //得到线段1的 Y截距 b的值
        val k2 = (anyLine.endPoint.y - anyLine.startPoint.y) / (anyLine.endPoint.x - anyLine.startPoint.x) // 得到线段2的斜率K的值
        //斜截式公式：y=kx+b ，进行简单转换一下就是： b= y-kx
        val b2 = anyLine.startPoint.y - anyLine.startPoint.x * k2 //得到线段2的 Y截距b的值。 在这里我们可以使用pointC的值，也可以使用pointD的值
        return  Point((b2 - b1) / (k1 - k2),k1 * point.x + b1)
    }

    /**
     * Finds the distance between a point [p0] and a line defined by points [p1] and [p2], and
     * the point on the line that is closest to the point [p0].
     */
    fun lineDistance(p0: Point): Pair<Float, Point> {
        val p1 = startPoint
        val p2 = endPoint
        // Line unit vector
        val line:Point = (p2 - p1) / (p2 - p1).mag()


        // get normal of the line
        val n = Point(-line.y, line.x)

        val distance = abs((p0 - p1) * n) / n.mag()

        val scalar: Float = ((p0 - p1) * line)

        val point = p1 +  line * scalar

        return Pair(distance, point)
    }

    fun toMathLine() = withTwoPoints(startPoint,endPoint)

    companion object{
        const val MIN_LENGTH=100
    }

}